/*

	Original script by Jillian Callahan.  As per the originator of this script upon which this is based
	this script is NOT INTENDED FOR SALE!
	
	Since then it has been extensively modified by:

	Revisors:
	---------------------------------------------------------------
	KR	Kisamin Resident
	CJ	Chant Juran

	
	Revision History:
	-------------------------------------------------------------------------
	9.4	KR	Cleaned up the formatting
			Removed the dependency on the ONOF and ONOFF variables, substituting dependency on g_iRunning
	9.5 KR	Put the gear down if throttle is set to 1.  Pull them up otherwise.
			Removed permissionResult variable which was unused.
			Plane will delete itself if it is rezzed under any group except the one with the key in k_kGroupKey
	9.6 KR	When the person unsits, stop animating them.
	9.7 KR	Removed the need for subordinate scripts in the landing gear for hiding/showing them.  Now prims named "landing gear" will
			automatically be shown or revealed upon gear extension or retraction.
	9.8 CJ  cleaned up the help file  and reduced the rotation speed main core   lines 148 and 153
			reduced from 200 to 140 pulse
	9.9 KR	removed the touch start code as it was way behind the times and the voice commands are the ones we use anyway.
			altered the behaviour of r 0 so that it is a synonym for r 10
			limited the speed setting to a maximum of 10
	9.11KR	Reduced memory usage by combining strings.  Also made the engine sound key a variable so it's easier to understand where
			it's being used.
			Removed dead code.
			Removed the public listener for commands.  Channel 0 is now only listened to when the color command is active.
	9.12KR	Added ability to move the crosshair forward and backwards.
			Added communications. Plane will now state when someone sits on it on the channel selected via the avatar's key
				Algorithm used is the same as that for the GM meter:  (integer)("0x" + llGetSubString (llGetOwner (), 0, 7))
			Added hook during self destruct so that effects can be easily done.
			
	------------- 9.11 released as Version 1.0 on 15-July-2012
	
	9.12 KR	Added hook for when the plane is self-destructed.

*/

string k_sSitText = "Fly!"; // When someone wants to sit here, this replaces that "Sit" text in the pie menu
vector k_vSittingPosition = <0.0, 0.0, -0.1>; //This is the position. X, Y, Z This is what you need to mess with.

// -------------------------------------------------------------------------
// KR 9.11 Added
key k_kEngineSound = "760cebbc-fd06-1f5f-fb01-720ae76e132b";
list k_lColours = ["red", <1., 0., 0.>
					,"green", <0., 1., 0.>
					, "blue", <0., 0., 1.>
					, "black", <0., 0., 0.>
					, "white", <1., 1., 1.>
					, "yellow", <1., 1., 0.>
					];
// -------------------------------------------------------------------------
// KR9.5: Put these in as variable to save memory later on.
string k_sCanopyOpenCommand = "canopy_open";
string k_sCanopyCloseCommand = "canopy_close";
string k_sSitAnimation = "Anim_Rickard_Roentgen-Pilot_Sit";
string k_sPilotCommand = "pilot";
// -------------------------------------------------------------------------

// key k_kRequiredGroupKey = "3fc27bc8-cd49-61ff-c7c0-3980745d68ba"; // Acme
key k_kRequiredGroupKey = "70926282-bcf6-54f6-5b49-4a4aa0584116"; // Remnant air group

key g_kAgent = NULL_KEY;

integer g_iRunning = FALSE;
//integer g_iOpen = 0;		KR 9.11 (removed)
integer g_iChannel = 50;

integer g_iGearListLength = 0;
integer g_iGearIsDown = TRUE;

integer	g_iColorListenerHandle = 0;

list	g_lGearLinks = [];

// KR 9.12 Self-destruct hook
isDead () {
	llDie ();
}

// KR 9.11 added moveCrossHair routine
moveCrossHair (vector in_vOffset) {
	integer i;
	for (i=0; i<=llGetNumberOfPrims (); i++) {
		if (llGetLinkName (i) == "crosshair") {
			list l_lParameters = llGetLinkPrimitiveParams (i,[PRIM_POS_LOCAL]);
			vector l_vLocalPosition = llList2Vector (l_lParameters, 0);
			l_vLocalPosition += in_vOffset;
			llSetLinkPrimitiveParams (i, [PRIM_POS_LOCAL, l_vLocalPosition]);
		}
	}
}

// KR 9.11 added processColor routine.
processColor (string in_sMessage) {

	if (in_sMessage == "u")
		moveCrossHair (<0., 0., 0.001>);
	else if (in_sMessage == "U")
		moveCrossHair (<0., 0., 0.01>);
	else if (in_sMessage == "d")
		moveCrossHair (<0., 0., -0.001>);
	else if (in_sMessage == "D")
		moveCrossHair (<0., 0., -0.01>);
	else if (in_sMessage == "r")
		moveCrossHair (<0., -0.001, 0.>);
	else if (in_sMessage == "R")
		moveCrossHair (<0., -0.01, 0.>);
	else if (in_sMessage == "l")
		moveCrossHair (<0., 0.001, 0.>);
	else if (in_sMessage == "L")
		moveCrossHair (<0., 0.01, 0.>);
// KR 9.12 added ability to move crosshair forward and back
	else if (in_sMessage == "f")
		moveCrossHair (<0.001, 0., 0.>);
	else if (in_sMessage == "F")
		moveCrossHair (<0.01, 0., 0.>);
	else if (in_sMessage == "b")
		moveCrossHair (<-0.001, 0., 0.>);
	else if (in_sMessage == "B")
		moveCrossHair (<-0.01, 0., 0.>);
	else {
	
		in_sMessage = llStringTrim (llToLower (in_sMessage), STRING_TRIM); // Remove any leading or trailing blanks
		
		vector l_vColourEntry = <99, 99, 99>;
		
		integer l_iNumberOfColourEntries = llGetListLength (k_lColours);
		integer i;
		
		for (i=0; i<l_iNumberOfColourEntries; i+=2)
			if (llSubStringIndex (llList2String (k_lColours, i), in_sMessage) == 0)
				l_vColourEntry = llList2Vector (k_lColours, i+1);
				
		if (l_vColourEntry == <99, 99, 99>) {
			if (llSubStringIndex (in_sMessage, "<") == 0)
				in_sMessage = llDeleteSubString (in_sMessage, 0, 0);
			
			if (llSubStringIndex (in_sMessage, ">") != -1)
				in_sMessage = llDeleteSubString (in_sMessage, llSubStringIndex (in_sMessage, ">"), -1);
		
			list l_lParts = llParseString2List (in_sMessage, [" ", ","], []);
			if (llGetListLength (l_lParts) != 3) {
				llOwnerSay ("Invalid color specification for the cross hair.");
				return;
			}
			
			string l_sRed = llStringTrim (llList2String (l_lParts, 0), STRING_TRIM);
			string l_sGreen = llStringTrim (llList2String (l_lParts, 1), STRING_TRIM);
			string l_sBlue = llStringTrim (llList2String (l_lParts, 2), STRING_TRIM);
			
			float l_fRed = (float)l_sRed / 255.;
			float l_fGreen = (float)l_sGreen / 255.;
			float l_fBlue = (float)l_sBlue / 255.;
			
			l_vColourEntry = <l_fRed, l_fGreen, l_fBlue>;
		}
		
		integer l_iNumberOfPrims = llGetNumberOfPrims ();
		
		for (i = 1; i <= l_iNumberOfPrims; i++) {
			if (llGetLinkName (i) == "crosshair")
				llSetLinkColor (i, l_vColourEntry, ALL_SIDES);
		}
	}
}


// KR9.7 Added gearSet routine
gearSet (float in_fAlpha) {
	integer i;
	for (i = 0; i < g_iGearListLength; i++)
		llSetLinkAlpha (llList2Integer (g_lGearLinks, i), in_fAlpha, ALL_SIDES);
	g_iGearIsDown = (in_fAlpha != 0.0);
}

init() {
	// KR9.5 The following code can not be tested in Eagles.  If the object is not Acme, it is returned.
	//			IF the object is not Remnant, it deletes itself.

	list l_lObjectDetails = llGetObjectDetails (llGetKey (), [OBJECT_GROUP]);
	if (llList2Key (l_lObjectDetails, 0) != k_kRequiredGroupKey) {
		llOwnerSay ("I am in the wrong group.  I should have group key " + (string)k_kRequiredGroupKey + " but instead of " + (string)llList2Key(l_lObjectDetails, 0));
		isDead ();
	}

	llSetCameraEyeOffset(<-9.0, 0.0, 3.0>);
	llSetCameraAtOffset(<0.0, 0.0, 1.75>);
	llSetSitText(k_sSitText);
	llSitTarget(k_vSittingPosition,ZERO_ROTATION);
}

instruct() {
	llOwnerSay("The mouse steers the plane.");
	llOwnerSay("\"SPEEDS are 1 to 0\" in chat, setting speeds in 10 m/s increments");
	llOwnerSay("\"SPEED 1\" sets speed to  10 m/s with Landing gear Down");
	llOwnerSay("WS to control the throttle.");
	llOwnerSay("W+S gives afterburner.");
	llOwnerSay("EC (or PgUp and PgDn) to move vertically.");
	llOwnerSay("\"SHIFT Z\" to toggle roll/strafe mode"); 
	llOwnerSay("AD in ROLL mode rolls the plane");
	llOwnerSay("AD in STRAFE mode moves the plane sideways");
	llOwnerSay("\"SHIFT X\" to begin\nor SHIFT N for color/move menu"); 
	llOwnerSay("There are gestures available for the chat commands"); 
	integer i;
	integer l_iNumNotecards = llGetInventoryNumber (INVENTORY_NOTECARD);
	for (i=0; i<l_iNumNotecards; i++)
		llGiveInventory (llGetOwner (), llGetInventoryName (INVENTORY_NOTECARD, i));
}

default {
	state_entry() {
		init();
		
		// KR9.7 find the various landing gear prims and save them in the list
		integer i;
		integer l_iPrimCount = llGetObjectPrimCount (llGetKey ());
		for (i = 1; i <= l_iPrimCount; i++)
			if (llGetLinkName (i) == "landing gear") {
				g_lGearLinks += i;
				llSetLinkAlpha (i, 1.0, ALL_SIDES);
			}
		
		// Save the length of the list so we don't have to recalculate it all the time
		g_iGearListLength = llGetListLength (g_lGearLinks);
		gearSet (1.0);
	}
	
	on_rez(integer times) {
		llResetScript ();
	}
	
	attach (key in_keyID) {
		llDetachFromAvatar ();
		isDead ();
	}
	
	changed(integer change) {
		if(change & CHANGED_LINK) {
			key av = llAvatarOnSitTarget();
			if( (av != NULL_KEY) && (g_kAgent == NULL_KEY) ) {
				// Fresh sit
				if (av == llGetOwner()) {
					g_kAgent = av;
					llListen(g_iChannel,"",llGetOwner (),"");
					llRequestPermissions (g_kAgent, PERMISSION_TRIGGER_ANIMATION);
					llOwnerSay("\"SHIFT X\" to begin piloting.");
					integer l_iChannel = (integer)("0x" + llGetSubString ((string)g_kAgent, 0, 7));
					if (l_iChannel>0) l_iChannel = -l_iChannel;
					if (l_iChannel==0) l_iChannel--;
					llSay (l_iChannel, "pilot:" + (string)g_kAgent);
				} else {
					llUnSit(av);
					llSay(0, "Sorry, only my owner may pilot me.");
				}
			} else if ( (av == NULL_KEY) && (g_kAgent != NULL_KEY) ) {
				// Probably stand
				// KR9.9: I don't think the THROTTLE and rundown messages are used anymore.
				//		  The pilot message is used to notify flight control about the pilot being gone now.
				
				llMessageLinked (LINK_SET, 0, k_sPilotCommand, NULL_KEY);
				llMessageLinked (LINK_SET, 1, "r", NULL_KEY);	// KR9.9 reset the throttle down to lowest setting.
				llStopAnimation (k_sSitAnimation);
				
				 // As per Erika, if the plane is running and the pilot ejects, delete the pilot.
				if (g_iRunning)
					isDead ();
				
				// Otherwise right it so that it's level and straight.  Keep whatever yaw it currently has, though.
				vector l_vRotation = llRot2Euler (llGetRootRotation ());
				llSetRot (llEuler2Rot (<0.0, 0.0, l_vRotation.z>));
	
				// And reset the script so that it's nice and fresh for the next pilot.
				llResetScript();
			}
		}
	}
	
	listen(integer g_iChannel, string name, key id, string msg) {
// KR 9.11 intercept things said on channel 0 and interpret them as color commands to the cross hairs.
		if (g_iChannel == 0) {
			processColor (msg);
			return;
		}
// KR 9.11 If we get down to here, it came in on the command channel and should be handled like a command.
		
		msg = llToLower(msg);

		//if running is true and a "r" is received do it
		if (g_iRunning && llGetSubString (msg, 0, 1) == "r " && llStringLength (msg) > 2) {
			integer l_iThrottle = (integer)llGetSubString (msg, 2, -1);
			
			// KR9.9: As per Chant, r 0 should be treated as r 10
			if (l_iThrottle == 0)
				l_iThrottle = 10;
			
			// KR9.9: limit the throttle to 100% (i.e. "10")
			if (l_iThrottle > 10)
				l_iThrottle = 10;
				
			llMessageLinked (LINK_SET, l_iThrottle, "r", id);

			// KR9.7 handle gear extension and retraction depending on our throttle setting.
			if (l_iThrottle >= 2) {
				if (g_iGearIsDown)
					gearSet (0.0);
			} else if (!g_iGearIsDown) {
				gearSet (1.0);
			}
			return;
		} else if ( msg == "roll") {
			if (g_iRunning) {
				// llOwnerSay ("Sending roll comand.");
				llMessageLinked (1, 0, "roll", g_kAgent); // tell the main script we're in roll mode
				return;
			} else
				llOwnerSay ("Either not started or currently holding.");
			
			// KR9.4 Optimized this so we only parse the msg once instead of twice for the onoff command
			// KR9.4 Removed dependency on ONOF and ONOFF variables.
		} else if (msg == "onoff" ) {
			if (!g_iRunning) {
				llLoopSound(k_kEngineSound,1.0);
				llMessageLinked (LINK_SET, 0, k_sPilotCommand, g_kAgent); // Tell the main script who's piloting
				llMessageLinked (LINK_SET, 1, "r", g_kAgent); // Set throttle to 1.
   
				// llMessageLinked (LINK_SET, 0, k_sCanopyCloseCommand, NULL_KEY); // Close the canopy

				llOwnerSay("Engine Running Say \"help\" for instructions.");
				g_iRunning = TRUE;
				if (g_iColorListenerHandle) {
					llListenRemove (g_iColorListenerHandle);
					g_iColorListenerHandle = 0;
					llSetTimerEvent (0.);
				}
				return;
			} else {
				llMessageLinked(LINK_SET, 0, k_sPilotCommand, NULL_KEY); // Tell the flight script that we no longer have a pilot
				llStopSound();
				llOwnerSay("Shutdown complete.");
				g_iRunning = FALSE;
				gearSet (1.0);					
				return;
			}
//	KR 9.11 - Added color command
		} else if (! g_iRunning && msg == "color") {
			if (g_iColorListenerHandle == 0) {
				llSetTimerEvent (120.);
				g_iColorListenerHandle = llListen (0, "", llGetOwner (), "");
			} else {
				llSetTimerEvent (0.);
				llListenRemove (g_iColorListenerHandle);
				g_iColorListenerHandle = 0;
			}
//	KR 9.11 - End of color command
		} else if ( msg == "help" ) {
			instruct();
			return;
		} 
	}
	
	run_time_permissions (integer in_iPermissions) {
		if (in_iPermissions & PERMISSION_TRIGGER_ANIMATION) {
			llStartAnimation (k_sSitAnimation);
		}
	}
	
	// KR 9.11 Added this routine so that "color mode" self limits it and shuts down the
	// listener in case the owner forgets to.
	
	timer () {
		if (g_iColorListenerHandle) {
			llOwnerSay ("Color changer timed out.");
			llListenRemove (g_iColorListenerHandle);
			g_iColorListenerHandle = 0;
		}
		
		llSetTimerEvent (0.);
	}
	
}